package sftp

import (
	"net"
	"time"

	"testkjcld.bhecard.com/gitlab/kit/esim/config"
	"testkjcld.bhecard.com/gitlab/kit/esim/log"

	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
)

type Client struct {
	logger log.Logger
	conf   config.Config

	defaultConf *Conf
}

type Conn struct {
	sshClient *ssh.Client
	fp        *sftp.File
	*sftp.Client
}

type Conf struct {
	Addr     string
	UserName string
	PassWord string
	MaxRetry int
}

type ClientOption func(*Client)

func NewClient(options ...ClientOption) *Client {
	c := &Client{}

	for _, option := range options {
		option(c)
	}

	if c.logger == nil {
		c.logger = log.NewLogger()
	}

	if c.conf == nil {
		c.conf = config.NewNullConfig()
	}

	c.defaultConf = &Conf{
		Addr:     c.conf.GetString("sftp_addr"),
		UserName: c.conf.GetString("sftp_name"),
		PassWord: c.conf.GetString("sftp_password"),
		MaxRetry: c.conf.GetInt("sftp_max_retry"),
	}

	return c
}

type ClientOptions struct{}

func (ClientOptions) WithConf(conf config.Config) ClientOption {
	return func(s *Client) {
		s.conf = conf
	}
}

func (ClientOptions) WithLogger(logger log.Logger) ClientOption {
	return func(s *Client) {
		s.logger = logger
	}
}

func (c *Client) Connect() (*Conn, error) {
	return c.ConnectWithConf(c.defaultConf)
}

func (c *Conn) Close() {
	if c.sshClient != nil {
		c.sshClient.Close()
		c.sshClient = nil
	}
	if c.Client != nil {
		c.Client.Close()
		c.Client = nil
	}

	if c.fp != nil {
		c.fp.Close()
	}
}

func (c *Conn) CloseRemoteFile() {
	if c.fp != nil {
		c.fp.Close()
	}
}

func (c *Client) ConnectWithConf(conf *Conf) (*Conn, error) {
	var (
		cli *Conn
		err error
	)
	if conf.MaxRetry == 0 {
		conf.MaxRetry = 3
	}
	for i := 0; i < conf.MaxRetry; i++ {
		cli, err = c._connect(conf)
		if err != nil {
			// 休眠500毫秒重试
			time.Sleep(500 * time.Millisecond)
			continue
		}
		break
	}

	return cli, err
}

func (c *Client) _connect(conf *Conf) (*Conn, error) {
	var (
		auth         []ssh.AuthMethod
		clientConfig *ssh.ClientConfig
		sshClient    *ssh.Client
		sftpClient   *sftp.Client
		err          error
	)
	// get auth method
	auth = make([]ssh.AuthMethod, 0)
	auth = append(auth, ssh.Password(conf.PassWord))

	clientConfig = &ssh.ClientConfig{
		User:    conf.UserName,
		Auth:    auth,
		Timeout: 30 * time.Second,
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
	}

	// connet to ssh
	if sshClient, err = ssh.Dial("tcp", conf.Addr, clientConfig); err != nil {
		return nil, err
	}

	// create sftp client
	if sftpClient, err = sftp.NewClient(sshClient); err != nil {
		sshClient.Close()
		return nil, err
	}

	return &Conn{
		Client:    sftpClient,
		sshClient: sshClient,
	}, nil
}
